package mcjunkin.gui;
import mcjunkin.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.border.*;
import java.io.*;
import java.awt.datatransfer.*;
import java.awt.Toolkit;

/**
	This is a JPanel that shows the output of the operation in a JTree.
*/

public class OutputWidget extends JPanel implements MatchListener{

	//hashtable to keep track of where we put things in the tree
	//	This contains not only top level tree node mappings, but the mapping
	// of all path names to their corresponding tree nodes
	private Hashtable<String,DefaultMutableTreeNode> lut;

	//swing elements
	private JTree tree;
	private DefaultMutableTreeNode top;
	private DefaultMutableTreeNode files;
	private DefaultMutableTreeNode folders;
	private JPopupMenu popup;
		
	//popup menu items
	private JMenuItem menuItemOpen;
	private JMenuItem menuItemCopy;


	/** Constructor builds the panel. */
	public OutputWidget(){

		//create a new hashlut
		lut = new Hashtable<String,DefaultMutableTreeNode>();
	
		//lay things out top to bottom		
		this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
	
		//add a jtree to the bottom
		top = new DefaultMutableTreeNode("Matches");
		tree = new JTree(top);
		tree.setRootVisible(false);
		tree.setCellRenderer(new CustomTreeCellRenderer());
		JScrollPane tmp = new JScrollPane(tree);
		tmp.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); //TLBR		
		this.add(tmp);

		//make the tree have a pretty border
		Border line = BorderFactory.createLineBorder(Color.DARK_GRAY);
		Border empty = new EmptyBorder(10, 10, 10, 10);
		CompoundBorder border = new CompoundBorder(line, empty);
		tree.setBorder(border);

		//add file and folder nodes
		DefaultTreeModel model = (DefaultTreeModel)tree.getModel();
		files = new DefaultMutableTreeNode(new TreeNode("Files",-1,0));
		folders = new DefaultMutableTreeNode(new TreeNode("Folders",-1,0));
		model.insertNodeInto(folders,top,top.getChildCount());
		model.insertNodeInto(files,top,top.getChildCount());
		tree.expandPath(new TreePath(top.getPath()));
		tree.setToggleClickCount(0);

		//add the actionlistener
		tree.addMouseListener(new MouseAdapter(){
			public void mouseClicked(MouseEvent e){
				if( e.getClickCount() == 2){
					try{ openSelectedNode(); }catch(IOException iex){
						iex.printStackTrace();
					}
				}
			}
		});

		//Create the popup menu.
		popup = new JPopupMenu();
		//open
		menuItemOpen = new JMenuItem("Open");
		menuItemOpen.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				try{ openSelectedNode(); }catch(IOException iex){
					iex.printStackTrace();
				}
			}
		});
		popup.add(menuItemOpen);
		//copy
		menuItemCopy = new JMenuItem("Copy");
		menuItemCopy.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				try{ copySelectedNode(); }catch(IOException iex){
					iex.printStackTrace();
				}
			}
		});
		popup.add(menuItemCopy);

		//Add listener to components that can bring up popup menus.
		MouseListener popupListener = new PopupListener();
		tree.addMouseListener(popupListener);

		
	}
	
	/** Adds a match between two files or folders. 
		@returns True if the add was successful, false otherwise
	*/
	public boolean addMatch(File f1, File f2){
		//get reference to the parent
		DefaultTreeModel model = (DefaultTreeModel)tree.getModel();

		//lookup f1 to see if it already exists
		DefaultMutableTreeNode tn1 = lut.get(f1.getPath());

		//if it does not already exist
		if(tn1 == null){
			//make a new treenode for f1
			tn1 = new DefaultMutableTreeNode(new TreeNode(f1.getPath(),
				f1.isDirectory() ? -1 : f1.length(),1));
			//add the treenode to the lookup table
			lut.put(f1.getPath(),tn1);
			//add the top treenode to the root
			DefaultMutableTreeNode node = (f1.isDirectory() ? folders : files);
			int row=node.getChildCount();
			model.insertNodeInto(tn1,node,row);
			tree.expandPath(new TreePath(node.getPath()));
			((TreeNode)node.getUserObject()).children=node.getChildCount();
		}
		else{
			TreeNode tn = (TreeNode)tn1.getUserObject();
			tn.children = tn1.getChildCount()+1;
		}
		//make f2 a child of f1
		DefaultMutableTreeNode tn2 = new DefaultMutableTreeNode(f2.getPath());
		lut.put(f2.getPath(),tn1);	//not a typo - f2 should map to the treenode
											//it is contained in, which is tn1
		model.insertNodeInto(tn2,tn1,tn1.getChildCount());
		//tree.expandPath(new TreePath(tn1.getPath()));

		return true;
	}

	/** Removes a match between two files or folders. 
		@returns True if the remove was successful, false otherwise
	*/
	public boolean removeMatch(File f1, File f2){
		//get reference to the parent
		DefaultTreeModel model = (DefaultTreeModel)tree.getModel();

		//lookup f1 - if it doesnt exist return false
		DefaultMutableTreeNode tn1 = lut.get(f1.getPath());
		if(tn1 == null) return false;
		
		//found or not
		boolean foundMatch = false;
		
		//see if f2 is a child of f1
		Enumeration e = tn1.children();
		DefaultMutableTreeNode nc;
		while(e.hasMoreElements()){
			nc = (DefaultMutableTreeNode)e.nextElement();
			if( f2.getPath().equals(((File)nc.getUserObject()).getPath()) ){
				model.removeNodeFromParent( nc );
				foundMatch=true;
				break ;
			}
		}
		
		//if no more matches, remove this match
		if(tn1.getChildCount() == 0){
			model.removeNodeFromParent(tn1);
		}
		
		return foundMatch;
	}


	/** Removes all matches.
	*/
	public void removeAll(){
		lut.clear();
		files.removeAllChildren();
		((TreeNode)files.getUserObject()).size = -1;
		((TreeNode)files.getUserObject()).children = 0;
		folders.removeAllChildren();
		((TreeNode)folders.getUserObject()).size = -1;
		((TreeNode)folders.getUserObject()).children = 0;
		reloadTree();
	}

	//helper method to reload the tree - now that I'm updating the tree model
	//directly I should never have to use this method, but I'll keep it around
	//just in case

	private void reloadTree(){
		//update the visual representation of the tree
		((DefaultTreeModel)tree.getModel()).reload();
		for (int i = 0; i < tree.getRowCount(); i++) {
			tree.expandRow(i);
		}
	}

	/** Convert the panel representation of the tree into a string */

	public String getStringRepresentation(){
		StringBuffer sb = new StringBuffer();
		//do folders
		DefaultMutableTreeNode[] nodes = {folders,files};
		//walk over FILES and FOLDERS
		for(int i=0; i < nodes.length; i++){		
			//walk through files and folders
			sb.append(i==0 ? "--Folders\n" : "--Files\n");
			for(int j=0; j < nodes[i].getChildCount(); j++){
				DefaultMutableTreeNode head = 
					(DefaultMutableTreeNode)nodes[i].getChildAt(j);
				TreeNode tn = (TreeNode)head.getUserObject();
				sb.append( tn.size + "\t" + tn.path);
				//walk through matches
				for(int k=0; k < head.getChildCount(); k++){
					DefaultMutableTreeNode m = 
						(DefaultMutableTreeNode)head.getChildAt(k);
					sb.append("\t"+m.getUserObject());
				}
				sb.append("\n");
			}
		}
		return sb.toString();
	}


	//a custom tree cell renderer that decides if something is a file or
	//folder
	private class CustomTreeCellRenderer extends DefaultTreeCellRenderer{

		public Component getTreeCellRendererComponent(JTree tree,
			Object value, boolean selected, boolean expanded,
			boolean leaf, int row, boolean hasFocus){

			DefaultMutableTreeNode tn = (DefaultMutableTreeNode)value;
			Object o = tn.getUserObject();
			String path = "";
			if(o instanceof String)
				path = (String)o;
			else if(o instanceof TreeNode)
				path = ((TreeNode)o).path;

			File f = new File(path);

			super.getTreeCellRendererComponent(tree, value,
			selected, expanded, f.isFile(), row, hasFocus);

			return this;
		}
	}
	
	//method opens the selected item in the jtree
	public void openSelectedNode() throws IOException{
		//get the currently selected path
		DefaultMutableTreeNode node = (DefaultMutableTreeNode)
			tree.getLastSelectedPathComponent();

		//dont do anything if this is the files or folders thing highlighted
		if(node == folders || node == files) return ;
			
		Object o = node.getUserObject();

		String path = "";
		if(o instanceof String) path = (String)o;
		if(o instanceof TreeNode) path = ((TreeNode)o).path;

		//get a reference to a desktop object
		Desktop desktop = null;
		if (Desktop.isDesktopSupported()) {
			desktop = Desktop.getDesktop();
		}
		//open the file
		desktop.open(new File(path));
	}

	//method copies the selected item in the jtree to the clipboard
	public void copySelectedNode() throws IOException{
		//get the currently selected path
		DefaultMutableTreeNode node = (DefaultMutableTreeNode)
			tree.getLastSelectedPathComponent();

		//dont do anything if this is the files or folders thing highlighted
		if(node == folders || node == files) return ;
			
		Object o = node.getUserObject();

		String path = "";
		if(o instanceof String) path = (String)o;
		if(o instanceof TreeNode) path = ((TreeNode)o).path;

		StringSelection stringSelection = new StringSelection (path);
		Clipboard clpbrd = Toolkit.getDefaultToolkit ().getSystemClipboard ();
		clpbrd.setContents (stringSelection, null);
	}



	//handles the popup. Taken from
	//http://docs.oracle.com/javase/tutorial/uiswing/components/menu.html#popup
	class PopupListener extends MouseAdapter {
		public void mousePressed(MouseEvent e) { maybeShowPopup(e); }
		public void mouseReleased(MouseEvent e) { maybeShowPopup(e); }
		private void maybeShowPopup(MouseEvent e) {
			if(e.isPopupTrigger()) popup.show(e.getComponent(),e.getX(),e.getY());
		}
	}

	//this is an object which formats the top level tree nodes
	class TreeNode{
		public String path;
		public long size;
		public int children;
		public TreeNode(String path, long size, int children){
			this.path = path;
			this.size = size;
			this.children = children;
		}
		
		public String toString(){
			if(size < 0) return path + ";"+" ("+children+")";
			return path + "; "+Lib.readableFileSize(size)+" ("+children+")";
		}
	}


}
